
package com.x52im.rbchat.logic.alarm;

import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
import android.graphics.Point;
import android.text.SpannableString;
import android.text.style.DynamicDrawableSpan;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.bumptech.glide.Glide;
import com.eva.android.ArrayListObservable;
import com.eva.android.widget.ARecyclerViewAdapter;
//import com.eva.android.widget.AsyncBitmapLoader;
import com.eva.android.widget.DataLoadableFragment;
import com.eva.android.widget.WidgetUtils;
import com.eva.android.widget.alert.AlertDialog;
import com.eva.epc.common.util.CommonUtils;
import com.noober.floatmenu.FloatMenu;
import com.noober.floatmenu.MenuItem;
import com.x52im.rbchat.Const;
import com.x52im.rbchat.MyApplication;
import com.x52im.rbchat.R;
import com.x52im.rbchat.cache.ImageCacheLoader;
import com.x52im.rainbowchat.http.logic.dto.GroupEntity;
import com.x52im.rainbowchat.http.logic.dto.RosterElementEntity;
import com.x52im.rbchat.cache.AlarmsProvider;
import com.x52im.rbchat.logic.alarm.impl.AlarmsViewModel;
import com.x52im.rbchat.logic.alarm.impl.BBSAlarmUIWrapper;
import com.x52im.rbchat.logic.alarm.impl.NetConnectionFaildHintWrapper;
import com.x52im.rbchat.logic.alarm.model.AlarmDto;
import com.x52im.rbchat.logic.chat_root.AbstractChattingActivity;
import com.x52im.rbchat.logic.chat_root.face.EmojiUtil;
import com.x52im.rbchat.logic.more.HelpActivity;
import com.x52im.rbchat.network.http.async.QueryFriendInfo;
import com.x52im.rbchat.utils.BroadcastToolKits;
import com.x52im.rbchat.utils.IntentFactory;
import com.x52im.rbchat.utils.PreferencesToolkits;
import com.x52im.rbchat.utils.ToolKits;

import java.util.ArrayList;
import java.util.List;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

/**
 * 首页“消息”显示和管理Fragment实现类.
 *
 * @author Jack Jiang(http://www.52im.net/space-uid-1.html)
 * @version 1.0
 */
public class AlarmsFragment extends DataLoadableFragment {

    private final static String TAG = AlarmsFragment.class.getSimpleName();

    /** 常量：删除item的长按菜单id */
    private final int CONTEXT_MENU_ID_DELETE$ALARMS_FOR_LIST_VIEW = Menu.FIRST;
    /** 常量：置顶item的长按菜单id */
    private final int CONTEXT_MENU_ID_ALWAYSTOP$ALARMS_FOR_LIST_VIEW = Menu.FIRST+1;
    /** 常量：取消置顶item的长按菜单id */
    private final int CONTEXT_MENU_ID_CANCEL$ALWAYSTOP$ALARMS_FOR_LIST_VIEW = Menu.FIRST+2;
    /** 常量：设置已读item的长按菜单id */
    private final int CONTEXT_MENU_ID_SETUP$HAS$READ_FOR_LIST_VIEW = Menu.FIRST+3;
    /** 常量：设置未读item的长按菜单id */
    private final int CONTEXT_MENU_ID_SETUP$UN$READ_FOR_LIST_VIEW = Menu.FIRST+4;

    /** 仿微信的ListView长按菜单弹出浮动菜单的显示参考基准位置 */
    private final Point floatMenuShowPoint_forListView = new Point();

    /** 组件：“消息”的列表 */
    private RecyclerView listView;
    /** 组件：“消息”的列表Adapter */
    private AlarmsListAdapter listAdapter;
    /** 组件：“消息”的列表布局管理器 */
    private LinearLayoutManager layoutManager = null;
    /** ViewModel："消息"列表数据ViewModel对象 */
    private AlarmsViewModel viewModel4Alarms;

    /** 布局：列表UI中没有消息时显示的UI（仅用于改善用户体验） */
    private LinearLayout llNoAlarms = null;

    /** 封装类：与IM服务器连接失败的提示封装实现对象（封装后可提高本类的代码可读性） */
    private NetConnectionFaildHintWrapper netConnectionFaildHintWrapper = null;
    /** 封装类：BBS世界频道的UI封装类实现对象（封装后可提高本类的代码可读性） */
    private BBSAlarmUIWrapper bbsAlarmUIWrapper = null;

    /** 变量：存放长按菜单时当前选中的列表Item索引值 */
    private int currentLongPressedItemIndex = -1;

    /**
     * 广播监听者：用于接收修改完成好友的备注后的的广播（用于刷新ui，及时显示）*/
    private BroadcastReceiver friendRemarkChangedBR = null;

    // [注意1]：Fragment中的onResume方法，在很多时候并不像Activity那样能被即时调用（比如当多个Fragment放置于
    //         ViewPager中时，子fragment的左右切换是不会触发Fragment的onResume方法，此时就需要与 setUserVisibleHint
    //         方法进行配合才能保证兜底（理论上，Fragment中的setUserVisibleHint和onResume方法，在Fragment
    //         可见性改变时总有一个会被调用），这样就能确保像Activity中那样不错过任何一次可性的触发
    // [注意2]：setUserVisibleHint方法中切不可调用涉及ui的代码，所有有关ui的代码都都应放在onCreateView内或之后调用，
    //         否则可能因Fragment在Activity中的生命周期问题，导致fragment中getActivity()或getContext()为null，
    //         但很适合在这样的方法中借助ViewModel+LiveData进行数据的加载和刷新（因其能自动处理Fragment和Activity
    //         的生命周期问题，因而这样用非常安全可靠）
    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        if (listAdapter != null && isVisibleToUser) {
            Log.i(TAG, "@@@@ AA-setUserVisibleHint已被调用，马上开始 loadAlarmsData... "+System.currentTimeMillis());
            viewModel4Alarms.loadAlarmsData();
        }
    }

    // 注意：Fragment中的onResume方法，在很多时候并不像Activity那样能被即时调用（比如当多个Fragment放置于
    // ViewPager中时，子fragment的左右切换是不会触发Fragment的onResume方法，此时就需要与 setUserVisibleHint
    // 方法进行配合才能保证兜底（理论上，Fragment中的setUserVisibleHint和onResume方法，在Fragment可见性改变时
    // 总有一个会被调用），这样就能确保像Activity中那样不错过任何一次可性的触发
    @Override
    public void onResume() {
        super.onResume();
        viewModel4Alarms.loadAlarmsData();
        if(bbsAlarmUIWrapper != null) {
            // BBS消息ui里需相关需要在onResume方法进行设置的东西
            bbsAlarmUIWrapper.onParentResume();
        }
        Log.i(TAG,"Alarms-AlarmsGragment中： onResume()被调用了！！！！！");
    }

    @Override
    public void onCreateAfter() {
        // 拿到本类对应的ViewModel对象，实现MVVM架构（注意：参数要用getActivity()，否则本Fragment
        // 所在的主activity中对此Model中的liveData的观察将不能被通知！）
        viewModel4Alarms = new ViewModelProvider(getActivity()).get(AlarmsViewModel.class);
    }

    @Override
    protected int contentLayout() {
        return R.layout.main_alarms_fragment;
    }

    @Override
    protected View initViews(@NonNull LayoutInflater inflater, @Nullable ViewGroup container){
        View view = super.initViews(inflater, container);

        // 列表UI中没有消息时显示的UI（仅用于改善用户体验）
        llNoAlarms = view.findViewById(R.id.main_alarms_list_noAlarmsLL);

        // 初始化显示列表
        this._initListView(view);
        // 载入额外要显示的数据
        this._initExtraDatas();
        // 初始化其它UI
        this._initOtherUI(view);
        // 初始化广播事件监听器
        this._initBroadCastReciever();
        // 初始化数据变动观察者
        this._initLiveDataObserver();

        return view;
    }

    /**
     * 初始化并添加各LiveData的观察者。
     *
     * 说明：1）根据ViewPager中的Fragment生命周期，除被缓存的fragment外，每次当fragment被销毁时，LiveData的
     *         观察者也是被销毁了（ViewModel的生命周期因依赖于getActivity(所对应的activity，所以它是不会被随
     *         便销毁掉的)），所以当Fragment再次被建立（本方法将再次因fragment的onCreateView()被调用而调用）
     *         ，也就不存在重复添加观察者的问题了。
     *      2）另外，当fragment再次被建立，即onCreateView()也再次被调用时，因ViewModel仍存活，再次添加观察者
     *         时，ViewModel中对应的LiveData会立即把fragment销毁期间多次postValue的最后一次post给通知观察者
     *         ，所以此时观察者就不会错过数据刷新通知了（至少是不会错过最后一次通知哦）！
     */
    private void _initLiveDataObserver()
    {
        Log.i(TAG, "@@@@3【AlarmsViewModel】AlarmsFragment中的_initLiveDataObserver，被调用了？？？？？？");

        // 数据模型LiveData绑定："消息"列表数据模型变动观察者，由此观察者来完成ui的刷新（这样就能保证最新消息能即时显示在界面上了）
        this.viewModel4Alarms.getAlarmsLiveData().observe(this, alarmsData -> {
            // 数据据载入完成，显示内容ui
            showContent();

            // 刷新列表（注意：列表数据的变动，也包括删除，当删除发生时，notifyVisibleDataSetChanged可能导致数组越界面问题
            // ，原因可能是该方法中定义的可视区仅仅是ui层的可视区，它不能跟已被变动的数据模型对应上，为了保险起见，无论何种类型
            // 的数据变动，调用notifyDataSetChanged()总是最安全可靠的，切记！）
//          notifyVisibleDataSetChanged(layoutManager, listAdapter);
            listAdapter.notifyDataSetChanged();

            Log.i(TAG, "@@@ XX位置AlarmsFragment中的viewModel4Alarms.getAlarmsLiveData()观察者被通知了，viewModel4Alarms.hash="+viewModel4Alarms.hashCode());
        });

        // 数据模型LiveData绑定：设置与服务器的连接改变观察者（当网络断开或再次连通时，底层通
        // 过此观察者通知UI进行显示等，用观察者的目的是使得底层能与UI进行代码解偶，而不是直接引用）
        this.viewModel4Alarms.getNetConnectionChangeLiveData().observe(this, o -> {
            // 网络连接状态改变时刷新ui
            if(netConnectionFaildHintWrapper != null)
                netConnectionFaildHintWrapper.refreshUI();

            Log.i(TAG, "@@@@3【AlarmsViewModel】AlarmsFragment中的getNetConnectionChangeLiveData.observe，被调用了？？？？？？");
        });

        // 数据模型LiveData绑定：设置BBS世界频道的数据变动观察者（通过此观察者可以在有新
        // 聊天消息或指令时能及时刷新界面等，用观察者的目的是使得数据模型能与UI进行代码解偶）
        this.viewModel4Alarms.getBbsAlarmLiveData().observe(this, alarmData -> {
            if(alarmData != null)
                bbsAlarmUIWrapper.refreshData(alarmData);

            Log.i(TAG, "@@@@3【AlarmsViewModel】AlarmsFragment中的getBbsAlarmLiveData.observe，被调用了？？？？？？");
        });
    }

    // 初始化显示列表
    private void _initListView(View v)
    {
        // 列表及列表的adapter
        listView = v.findViewById(R.id.main_alarms_list_listView);
        // 使RecyclerView保持固定的大小,这样会提高RecyclerView的性能
        listView.setHasFixedSize(true);

        //设置分割线（使用官方的一个默认实现，不然自已来画就太烦了）
        DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(c(), DividerItemDecoration.VERTICAL);
        dividerItemDecoration.setDrawable(ContextCompat.getDrawable(c(), R.drawable.main_alarms_listview_divider));
        listView.addItemDecoration(dividerItemDecoration);

        // 设置线性布局管理器
        listView.setLayoutManager(layoutManager = new LinearLayoutManager(c()));

        // 实例化Adapter
        listAdapter = new AlarmsListAdapter(a(), createOnItemClickListener(), creteOnLongClickListener());

        // 设置Adapter
        listView.setAdapter(listAdapter);
//        // 强制刷新显示（目的是触发内容改变并通知观察者）
//        listAdapter.notifyDataSetChanged();
    }

    // 初始化其它UI
    private void _initOtherUI(View view)
    {
        //**** 网络连接状态UI包装实现类
        netConnectionFaildHintWrapper = new NetConnectionFaildHintWrapper(this, view);

        //**** BBS消息提示UI包装实现类
        bbsAlarmUIWrapper = new BBSAlarmUIWrapper(this, view);
    }

    // 载入额外要显示的数据
    protected void _initExtraDatas()
    {
        //** 自230824起，本加载已放至ChatBaseEventListener中的afterLinkSucess方法内调用，确保在离线消息加载完成前执行，否则极端情况下会出现首页消息重复item的情况）
//      // 载入历史数据、预定义数据等
//      this.viewModel4Alarms.loadAlarmsDataOnce();
    }

    /**
     * 初始化各广播监听器。
     */
    protected void _initBroadCastReciever()
    {
        friendRemarkChangedBR = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                String friendUid = intent.getStringExtra("friend_uid");
                String friendNicknameWithRemark = intent.getStringExtra("friend_nickname_with_remark");
                Log.i(TAG, "【好友备注更新】收到 (friendUid="+friendUid+"，friendNicknameWithRemark="+friendNicknameWithRemark+") 已修改完成的广播通知！");

                // 当列表中存在该好友的item时才刷新（不要浪费性能嘛）
                int index = imc().getAlarmsProvider().getAlarmIndex(AlarmType.friendChat, friendUid);
                if(index != -1) {
                    // 更新列表item显示
                    listAdapter.notifyItemChanged(index);
//                    listAdapter.notifyDataSetChanged();
                    Log.i(TAG, "【好友备注更新】当前列表item ui显示刷新成功！");
                }
            }
        };
        // 注册广播监听者：用于接收修改完成好友的备注后的广播通知
        BroadcastToolKits.friendRemarkChanged_REGISTER(c(), friendRemarkChangedBR);
    }

    @Override
    public void onDestroyView() {
        // 取消注册广播监听者：修改完成好友的备注后的广播
        BroadcastToolKits.friendRemarkChanged_UNREGISTER(c(), friendRemarkChangedBR);
        Log.i(TAG,"Alarms-AlarmsGragment中： onDestroyView()被调用了！！！！！");
        super.onDestroyView();
    }

    // 接收父Activity传递进来的长按事件处理(保存仿微信的长按弹出浮动菜单的显示基准位置点)
    // Fragment本身是没有 dispatchTouchEvent 这个长按事件回调方法的，只能借助所在Activity的传递
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if(ev.getAction() == MotionEvent.ACTION_DOWN) {
            // 保存此时的长按点信息
            floatMenuShowPoint_forListView.x = (int) ev.getRawX();
            floatMenuShowPoint_forListView.y = (int) ev.getRawY();
        }
        return super.dispatchTouchEvent(ev);
    }

    /**
     * 列表的长按事件处理监听器（目前在这个事件里，用于显示长按弹出菜单）。
     *
     * @return
     */
    private ARecyclerViewAdapter.OnLongClickListener creteOnLongClickListener(){

        // 列表的长按事件处理（当前使用的是仿微信样式的长按弹出浮动菜单：之所以这样是因为系统默认的长按弹出菜
        // 单在小米、华为等一些机型上，因为手机厂商的ROOM深度定制问题，导致了系统API的兼容性——弹出的菜
        // 单位置错乱，所以只能自已实现菜单了，不然各家对系统默认的长按菜单表现可能都不一样，没有办法！）
        ARecyclerViewAdapter.OnLongClickListener ls = position -> {
            if (listAdapter.checkIndexValid(position)) {
                // 保存当前长按选中的列表行索引号
                currentLongPressedItemIndex = position;

                AlarmDto d = listAdapter.getListData().get(currentLongPressedItemIndex);

                final ArrayList<MenuItem> menuItemList  = new ArrayList<>();
                // 准备好弹出菜单项
                addContextMenuItems_forListView(menuItemList, d);

                FloatMenu floatMenu = new FloatMenu(a(), 130);
                floatMenu.items(menuItemList);
                // 弹出菜单项点击事件处理
                floatMenu.setOnItemClickListener((v, position1) -> {
                    MenuItem mi = menuItemList.get(position1);
                    if(mi != null)
                        fireContextMenuItemSelected_forListView(mi);
                });

                // 显示弹出菜单
                floatMenu.show(floatMenuShowPoint_forListView);
            }

            // return true if the callback consumed the long click, false otherwise
            return true; // 返回true就可解决与点击事件的冲突哦
        };
        return ls;
    }

    /**
     * 列表的点击事件处理监听器。
     *
     * @return
     */
    private ARecyclerViewAdapter.OnItemClickListener createOnItemClickListener(){
        ARecyclerViewAdapter.OnItemClickListener ls = position -> {
            AlarmDto amd = listAdapter.getItem(position);
            if(amd == null)
                return;

            // 是加好友的请求：进入好友请求处理页面中
            if(amd.getAlarmType() == AlarmType.addFriendRequest) {
                // 进入好友请求信息列表管理界面
                startActivity(IntentFactory.createVerificationRemindersActivityIntent(a()));
            }
            // 陌生人/临时聊天消息查看
            else if(amd.getAlarmType() == AlarmType.guestChat) {
                String fromUid = amd.getDataId(), fromNickname = amd.getTitle();
                // 如果这条陌生人聊天信息对应的人，已经是好友了，则先给于提示，并删除这条遗留的陌生人聊天记录后，自动进入正常的好友聊天界面
                if (imc().getFriendsListProvider().isUserInRoster2(fromUid)) {
                    new AlertDialog.Builder(c())
                            .setTitle($$(R.string.general_prompt))
                            .setMessage("\""+fromNickname+"\"已经是你的好友了，将自动删除遗留的陌生人聊天信息后进入好友聊天界面。")
                            .setPositiveButton($$(R.string.general_got_it), (dialog, which) -> {
                                // 从本界面的列表中”“数据删除此行遗留的陌生人聊天信息
                                imc().getAlarmsProvider().removeAlarm(c(), position, true);
                                // 进入聊天界面
                                gotoSingleChattingActivity(a(), fromUid, fromNickname, null);
                            })
                            .show();
                } else {
                    // 正常进入聊天界面
                    gotoSingleChattingActivity(a(), fromUid, fromNickname, null);
                }
            }
            //收到一对一好友聊天信息
            else if(amd.getAlarmType() == AlarmType.friendChat) {
                String friendUid = amd.getDataId(), friendNickname = amd.getTitle();
                gotoSingleChattingActivity(a(), friendUid, friendNickname, null);
//              a().startActivity(IntentFactory.createChatIntent(a(), friendUid));

                //** 注意：目前重置首页“消息”、“好友”列表里的未读消息数是在聊天界面的代码里实现的，这样才是最准的！
            }
            else if(amd.getAlarmType() == AlarmType.addFriendBeReject) {
                // 点击进入个人信息查看界面
                QueryFriendInfo.gotoWatchUserInfo(a(), amd.getDataId(), null);
            }
            // 群聊消息
            else if(amd.getAlarmType() == AlarmType.groupChat) {
                String gid = amd.getDataId(), gname = amd.getTitle();
                Log.i(TAG, "从首页点击进入群聊：gid="+gid+", gname="+gname);
                if(gid != null && gname != null)
                    startActivity(IntentFactory.createGroupChatIntent(a(), gid, gname));
            }
            else if(amd.getAlarmType() == AlarmType.systemDevTeam) {
                startActivity(IntentFactory.createHelpActivityIntent(a(), HelpActivity.FININSH_VIEWPAGE_FINISHACTIVITY, true));
            }
            else if(amd.getAlarmType() == AlarmType.systemQNA) {
                startActivity(IntentFactory.createCommonWebActivity2Intent(a(), ToolKits.isChinese_SimplifiedChinese()? Const.RBCHAT_QNA_CN_URL:Const.RBCHAT_QNA_EN_URL, $$(R.string.main_alarms_list_view_qna_title), true));
            }
            else {
                WidgetUtils.showToast(a(), "无效的会话类型！");
            }
        };
        return ls;
    }

    /**
     * <p>
     * 实现加入所有列表长按菜单项.<br>
     *
     * 子类如果需要加入长按菜单项，请重写本方法，默认什么出不做，子类实现时只需要加入
     * 如：“menu.add(Menu.NONE, VERIFY_ID, Menu.NONE, name);”样的代码即可加入一个菜单项。
     * </p>
     */
    protected void addContextMenuItems_forListView(ArrayList<MenuItem> menuItemsList, AlarmDto d)
    {
        if(menuItemsList == null)
            return;

        if (d.getAlarmType() == AlarmType.groupChat
                || d.getAlarmType() == AlarmType.friendChat
                || d.getAlarmType() == AlarmType.guestChat) {
            // 已经是置顶的，就显示取消置顶菜单项
            if (d.isAlwaysTop()) {
                menuItemsList.add(new MenuItem()
                        .setItem("取消置顶")
                        .setItemResId(R.drawable.main_alarms_menu_item_uptop_ico)
                        .setItemActionId(CONTEXT_MENU_ID_CANCEL$ALWAYSTOP$ALARMS_FOR_LIST_VIEW)
                );
            } else {
                menuItemsList.add(new MenuItem()
                        .setItem("置顶聊天")
                        .setItemResId(R.drawable.main_alarms_menu_item_top_ico)
                        .setItemActionId(CONTEXT_MENU_ID_ALWAYSTOP$ALARMS_FOR_LIST_VIEW));
            }

            // 未读数大于0就可以设为未读了
            if (CommonUtils.getIntValue(d.getFlagNum(), 0) > 0) {
                menuItemsList.add(new MenuItem()
                        .setItem("设为已读")
                        .setItemResId(R.drawable.main_alarms_menu_item_read_ico)
                        .setItemActionId(CONTEXT_MENU_ID_SETUP$HAS$READ_FOR_LIST_VIEW));
            } else {
                menuItemsList.add(new MenuItem()
                        .setItem("设为未读")
                        .setItemResId(R.drawable.main_alarms_menu_item_unread_ico)
                        .setItemActionId(CONTEXT_MENU_ID_SETUP$UN$READ_FOR_LIST_VIEW));
            }
        }

        menuItemsList.add(new MenuItem()
                .setItem($$(R.string.general_delete))
                .setItemResId(R.drawable.main_alarms_menu_item_del_ico)
                .setItemActionId(CONTEXT_MENU_ID_DELETE$ALARMS_FOR_LIST_VIEW));
    }

    /**
     * 本方法执行对ListView选中的长按弹出菜单项的事件处理.<br>
     *
     * @param item
     */
    protected void fireContextMenuItemSelected_forListView(com.noober.floatmenu.MenuItem item)
    {
        // 本本ListView的数据模型
        ArrayListObservable<AlarmDto> alarmDatas = imc().getAlarmsProvider().getAlarmsData();
        // 从数据模型中取出对应索引的对象
        AlarmDto currentItemDto = alarmDatas.getDataList().get(currentLongPressedItemIndex);

        switch (item.getItemActionId()) {
            // 置顶
            case CONTEXT_MENU_ID_ALWAYSTOP$ALARMS_FOR_LIST_VIEW: {
                // 设置
                imc().getAlarmsProvider().setAlwaysTop(c(), true, currentItemDto);
                // 刷新列表ui
//              listAdapter.notifyDataSetChanged();
                listAdapter.notifyItemChanged(currentLongPressedItemIndex);
                break;
            }
            // 取消置顶
            case CONTEXT_MENU_ID_CANCEL$ALWAYSTOP$ALARMS_FOR_LIST_VIEW: {
                // 设置
                imc().getAlarmsProvider().setAlwaysTop(c(), false, currentItemDto);
                // 刷新列表ui
//              listAdapter.notifyDataSetChanged();
                listAdapter.notifyItemChanged(currentLongPressedItemIndex);
                break;
            }
            // 设置已读
            case CONTEXT_MENU_ID_SETUP$HAS$READ_FOR_LIST_VIEW: {
                // 请除未读数
                imc().getAlarmsProvider().setupReadOrUnread(c(), currentItemDto, true);
                // 通过通知LiveData的观察者，实现ui层的未读数变动通知
                // * 目前此观察者有两个：
                // * 1）是本AlarmsFragment中（见本类中的_initLiveDataObserver()方法）；
                // * 2）是本Fraggment所处的主界面MainActivity中(详见initLiveDataObserver()方法，用于更新主界面标题栏、下方导航栏上的总未读数)）。
                viewModel4Alarms.getAlarmsLiveData().postValue(null);
                break;
            }
            // 设置未读
            case CONTEXT_MENU_ID_SETUP$UN$READ_FOR_LIST_VIEW: {
                // 设置未读数1
                imc().getAlarmsProvider().setupReadOrUnread(c(), currentItemDto, false);
                // 通过通知LiveData的观察者，实现ui层的未读数变动通知
                // * 目前此观察者有两个：
                // * 1）是本AlarmsFragment中（见本类中的_initLiveDataObserver()方法）；
                // * 2）是本Fraggment所处的主界面MainActivity中(详见initLiveDataObserver()方法，用于更新主界面标题栏、下方导航栏上的总未读数)）。
                viewModel4Alarms.getAlarmsLiveData().postValue(null);
                break;
            }
            // 删除
            case CONTEXT_MENU_ID_DELETE$ALARMS_FOR_LIST_VIEW: {
                if(currentItemDto != null) {
                    // 系统预定义“消息”无法删除提示
                    if(AlarmsProvider.isSystemDefineAlarm(currentItemDto.getAlarmType())) {
                        WidgetUtils.showToast(c(), $$(R.string.main_alarms_list_view_cant_delete_systemdefine), WidgetUtils.ToastType.INFO);
                        return;
                    }
                }

                new AlertDialog.Builder(c())
                        .setTitle($$(R.string.general_are_u_sure))
                        .setMessage($$(R.string.main_alarms_list_view_delete_item_hint))
                        .setPositiveButton($$(R.string.general_ok), (dialog, which) -> {
                            // 从列中删除数据（以下代码相当于直接从alarmDatas中删除，因为alarmDatas就是
                            // MyApplication.getInstance(this).getAlarmsProvider()中数据结构句柄的引用）
                            imc().getAlarmsProvider().removeAlarm(c(), currentLongPressedItemIndex, true);
                        })
                        .setNegativeButton($$(R.string.general_cancel), null)
                        .show();
            }
            default:
                break;
        }
    }

    /**
     * 打开单聊聊天界面。
     *
     * @param c 上下文对象引用
     * @param uid 对方的uid
     * @param name 对方的昵称（当对方是好友时，本参数无效可设null）
     * @param highlightOnceMsgFingerprint 该指纹码的消息将高亮显示一次（当前用于搜索功能中进到聊天界面时）
     */
    public static void gotoSingleChattingActivity(Context c, String uid, String name, String highlightOnceMsgFingerprint){
        if(uid != null) {
            // 这条系统消息点开时如果该人员已是好友则下入正式聊天界面
            Intent i = null;
            if(MyApplication.getInstance2().getIMClientManager().getFriendsListProvider().isUserInRoster2(uid)) {
                i = IntentFactory.createChatIntent(c, uid);
            }
            // 如果已经不是好友了，则自动打开临时聊天消息界面
            else {
                i = IntentFactory.createTempChatIntent(c, uid, name);
            }

            i.putExtra(AbstractChattingActivity.INTENT_KEY_HIGHLIGHT_ONECE_MSG$FINGERPRINT, highlightOnceMsgFingerprint);
            c.startActivity(i);
        }
    }

    /**
     * 打开群聊聊天界面。
     *
     * @param c 上下文对象引用
     * @param gid 群id
     * @param ge 群基本信息（当本参数为空时，将会根据参数gid从群缓存列表中读取群基本信息缓存数据）
     * @param highlightOnceMsgFingerprint 该指纹码的消息将高亮显示一次（当前用于搜索功能中进到聊天界面时）
     */
    public static void gotoGroupChattingActivity(Context c, @NonNull String gid, @Nullable GroupEntity ge, String highlightOnceMsgFingerprint){
        GroupEntity g = ge;
        if(g == null){
            g = MyApplication.getInstance2().getIMClientManager().getGroupsProvider().getGroupInfoByGid(gid);
        }

        if(g != null) {
            String gname = g.getG_name();
            if (gid != null && gname != null) {
                Intent i = IntentFactory.createGroupChatIntent(c, gid, gname);
                i.putExtra(AbstractChattingActivity.INTENT_KEY_HIGHLIGHT_ONECE_MSG$FINGERPRINT, highlightOnceMsgFingerprint);
                c.startActivity(i);
            }
        }
    }


    //-------------------------------------------------------------------------- 以下是内部类

    /**
     * 自定义"消息"明细列表的adapter类实现.
     */
    private class AlarmsListAdapter extends ARecyclerViewAdapter<AlarmDto>{

        public AlarmsListAdapter(Activity context
                , ARecyclerViewAdapter.OnItemClickListener itemClickListener
                , ARecyclerViewAdapter.OnLongClickListener longClickListener) {
            super(context, R.layout.main_alarms_fragment_list_item, itemClickListener, longClickListener);
        }

        @Override
        // 本方法只在父类AListAdapter的构造方法里被调用一次
        protected List<AlarmDto> createListData() {
            // 本ListView的数据模型
            ArrayListObservable<AlarmDto> alarmDatas = imc().getAlarmsProvider().getAlarmsData();
            // 从数据模型中取出数据列表（特别注意：此数据集合是首页"消息"列表数据全局缓存
            // 对象的浅拷贝，后续数据模型内数据的变动，列表可以直接读取到，无法进行数据传递）
            return alarmDatas.getDataList();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            return new LinearViewHolder(layoutInflater.inflate(itemResId, parent,false));
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
            // 别忘了调用父类方法
            super.onBindViewHolder(viewHolder, position);

            // 正在操作的viewHolder对象
            LinearViewHolder holder = (LinearViewHolder)viewHolder;

            // 正在操作的列表行的数据集
            final AlarmDto rowData = listData.get(position);
            // 是否已设置"消息免打扰"
            final boolean isSilent = isSilent(rowData.getAlarmType(), rowData.getDataId());
            // 是否存在消息未读（true表示没有未读）
            boolean noUnread = (CommonUtils.isStringEmpty(rowData.getFlagNum(), true) || CommonUtils.getIntValue(rowData.getFlagNum(), 0)<=0);


            //---------------------------------------------------------------- （3）为item的UI组件设置显示值
            // 给标签设置值，以供用户查看
            holder.viewTitle.setText(rowData.getTitle());
            holder.viewDate.setText(rowData.getDateHuman());

            // 聊天内容标签显示（含自定义图标表情的处理和显示）
            String alarmContent = rowData.getAlarmContent();
//			viewMsgContent.setText(rowData.getAlarmContent());
            /* FFF emoji图片表情转换 */
//			System.out.println("-----textsize="+viewMsgContent.getTextSize());
//			int contentFontSize = ToolKits.sp2px(context, viewMsgContent.getTextSize());
            if(!CommonUtils.isStringEmpty(alarmContent, true)) {
                int emojiSize = (int) (holder.viewMsgContent.getTextSize() * 1.2);// 经测试getTextSize得到的已经是px，无需再转换。 *1.2 是因为得到的图片看起来比文字略小，所以增大20%
                SpannableString ss = EmojiUtil.replaceEmoticons(context, alarmContent, emojiSize, emojiSize, DynamicDrawableSpan.ALIGN_BOTTOM);
                holder.viewMsgContent.setText(ss);
            }
            else{
                holder.viewMsgContent.setText(alarmContent == null?"":alarmContent);
            }

            // 未读消息数的显示
            if(noUnread) {
                holder.viewFlagNum.setVisibility(View.GONE);
                holder.viewFlagDot.setVisibility(View.GONE);
//                holder.viewMsgPrefix.setVisibility(View.GONE);
//                holder.viewMsgPrefix.setText("");
            }
            else {
                // 如果已设置"消息免打扰"，就显示红点+消息内容前缀"[xx条]"
                if(isSilent){
                    holder.viewFlagNum.setVisibility(View.GONE);
                    holder.viewFlagDot.setVisibility(View.VISIBLE);
//                    holder.viewMsgPrefix.setVisibility(View.VISIBLE);
//                    holder.viewMsgPrefix.setText("["+rowData.getFlagNum()+"条消息]");
                }
                // 否则正常显示未读消息数红色小气泡
                else{
                    holder.viewFlagNum.setVisibility(View.VISIBLE);
                    holder.viewFlagDot.setVisibility(View.GONE);
//                    holder.viewMsgPrefix.setVisibility(View.GONE);
//                    holder.viewMsgPrefix.setText("");
                }

                holder.viewFlagNum.setText(rowData.getFlagNum());
            }

            // 设置消息前缀的显示内容
            String msgPrefix = "";
            int msgPrefixColor = R.color.gray_dark_for_text;
            int msgPrefixVisible = View.GONE;
            if(rowData.isAtMe()) {
                msgPrefixVisible = View.VISIBLE;
                msgPrefixColor = R.color.red_for_at;
                msgPrefix = "[有人@我]";
            }
            // TODO: 稍后实现草稿功能！（注：Jack建议草稿状态单独存放于其它途径，比如preference中，因草稿特性不属于消息本身，因而不应存放于消息记录中！）
//            else if(rowData.isHasDraft()) {
//                msgPrefixVisible = View.VISIBLE;
//                msgPrefixColor = R.color.red_for_at;
//                msgPrefix = "[草稿]";
//            }
            else if(!noUnread && isSilent) {
                msgPrefixVisible = View.VISIBLE;
                msgPrefixColor = R.color.gray_dark_for_text;
                msgPrefix = "["+rowData.getFlagNum()+"条消息]";
            }
            holder.viewMsgPrefix.setVisibility(msgPrefixVisible);
            holder.viewMsgPrefix.setText(msgPrefix);
            holder.viewMsgPrefix.setTextColor(getResources().getColor(msgPrefixColor));

            // 是否置顶的图标显示
            holder.viewIsAlwaysTop.setVisibility(rowData.isAlwaysTop()?View.VISIBLE:View.GONE);
            // 默认标题栏左边的标签是不需要显示的
            holder.viewTitleLeftFlag.setVisibility(View.GONE);
            // 消息提示是否静音的图标显示
            holder.viewSilentIcon.setVisibility(isSilent ? View.VISIBLE : View.GONE);
//          holder.viewSilentIcon.setVisibility(View.GONE);

            // 根据Glide官方指南的说明：https://muyangmin.github.io/glide-docs-cn/doc/getting-started.html#%E5%9C%A8-listview-%E5%92%8C-recyclerview-%E4%B8%AD%E7%9A%84%E4%BD%BF%E7%94%A8
            // 由于RecycleView中view的复用问题，为了防止图片显示错乱，如果它在之前的位置上用 Glide 进行过加载操作，那么在新的位置上要去执行一个新的加载操作或调用 clear()，
            // 实测通过clear()或通过Glide.with(context).load(默认占位图的资源id)两种方式都可以解决错乱问题，但clear更好，因为它不会在发生图片显示时闪烁一下的问题
//          Glide.with(context).load(R.drawable.main_alarms_sns_addfriendreject2r_message_icon).into(holder.viewIcon); // 解决方法1
            Glide.with(context).clear(holder.viewIcon);// 解决方法2

            // 按照提示类型的不同进行各自的数据设置逻辑
            if(rowData.getAlarmType() == AlarmType.addFriendBeReject) {
                holder.viewIcon.setImageResource(R.drawable.main_alarms_sns_addfriendreject2r_message_icon);
            }
            // 正常的好友聊天消息
            else if(rowData.getAlarmType() == AlarmType.friendChat) {
                // 默认占位图
                holder.viewIcon.setImageResource(R.drawable.main_alarms_chat_message_icon);
//                // 消息提示是否静音的图标显示
//                holder.viewSilentIcon.setVisibility(isSilent ? View.VISIBLE : View.GONE);

                String friendUid = rowData.getDataId();
                // 用户的头像文件名（这个文件名目前仅用于Glide缓存图片时作为signature字段使用）
                String fileNameForUserAvatar = null;

                if (friendUid != null) {
                    // 优先从好友数据中读取对方头像文件名，确保文件名是最新的
                    final RosterElementEntity friendRee = imc().getFriendsListProvider().getFriendInfoByUid2(friendUid);
                    if (friendRee != null) {
                        fileNameForUserAvatar = friendRee.getUserAvatarFileName();
                        // 优先使用好友列表中的昵称，确保首页"消息"列表中显示的好友聊天item标题能尽可能保持最新数据 @since 7.3
                        holder.viewTitle.setText(friendRee.getNickNameWithRemark());
                    }

                    boolean dontDiskCache = false;
                    // 该字段为空可能是该用户没有设置头像，也可能是有可能是对方已把"我"删除，因而对方不在我
                    // 的好友列表里了，所以好友数据不存在，头像文件名也就取不到了，这种情况取头像时，就把它当陌生人聊
                    // 天模式去取头像，即dontDiskCache为true——确保相同url情况下至少app重启后能强行拉取一次最新头像
                    if (CommonUtils.isStringEmpty(fileNameForUserAvatar, true)) {
                        dontDiskCache = true;
                    }
                    // 加载用户头像(有头像文件名fileNameForUserAvatar作为Glide加载时的signature，可以保证在好友更改头像后，能及时加载到最新图片)
                    ImageCacheLoader.loadAvatarImgWithGlide(getContext(), friendUid, fileNameForUserAvatar
                            , holder.viewIcon, 7 // 圆角单位是dp，（相当于xxhdpi下的21像素）
                            , R.drawable.main_alarms_chat_message_icon, dontDiskCache, false);
                }
            }
            // 陌生人/临时聊天
            else if(rowData.getAlarmType() == AlarmType.guestChat) {
                String fromUid = rowData.getDataId();
                // 用户的头像文件名（这个文件名目前仅用于Glide缓存图片时作为signature字段使用）
                String fileNameForUserAvatar = null;
                // 是系统通知账号，就不显示陌生人标签
                if(!ToolKits.isSystemAdmin(fromUid)) {
                    // 标题左边的"陌"标签显示
                    holder.viewTitleLeftFlag.setVisibility(View.VISIBLE);
                    holder.viewTitleLeftFlag.setText($$(R.string.main_alarms_list_view_item_title_flag_guest));
                    holder.viewTitleLeftFlag.setBackgroundResource(R.drawable.main_alarms_item_flag_guest_bg_ico);
                }
                // 默认占位图
                holder.viewIcon.setImageResource(R.drawable.main_alarms_tenpchat_message_icon);
//                // 消息提示是否静音的图标显示
//                holder.viewSilentIcon.setVisibility(isSilent ? View.VISIBLE : View.GONE);

                if (fromUid != null) {
                    // 对于陌生人来说，extra1String中，存放的就是可能最新头像文件名（在查看最新用户资料时设置进来的）
                    fileNameForUserAvatar = rowData.getExtraString1();//MyApplication.getInstance2().getIMClientManager().getAlarmsProvider().getExtra1String(AlarmType.guestChat, fromUid);
                    // 加载用户头像(不从sd卡读取缓存（dontDiskCache=true将确保app启动后首次加载时能从网络加载一次）)
                    ImageCacheLoader.loadAvatarImgWithGlide(getContext(), fromUid, fileNameForUserAvatar
                            , holder.viewIcon, 7 // 圆角单位是dp，（相当于xxhdpi下的21像素）
                            , R.drawable.main_alarms_tenpchat_message_icon, true, false);
                }
            }
            // 群聊聊天
            else if(rowData.getAlarmType() == AlarmType.groupChat) {
                // dataId中存放在就是群聊id
                String gid = rowData.getDataId();
                // 默认占位图
                holder.viewIcon.setImageResource(R.drawable.groupchat_groups_icon_default);
//              // 消息提示是否静音的图标显示
//              holder.viewSilentIcon.setVisibility(isSilent ? View.VISIBLE : View.GONE);

                if(gid != null) {
                    // 加载群头像
                    ImageCacheLoader.loadGroupImgWithGlide(Glide.with(getContext()), gid, holder.viewIcon, 7 , false, -1);// 圆角单位是dp，（相当于xxhdpi下的21像素）
                }
            }
            else if(rowData.getAlarmType() == AlarmType.addFriendRequest)
                holder.viewIcon.setImageResource(R.drawable.main_alarms_sns_addfriendrequest_message_icon);
            else if(rowData.getAlarmType() == AlarmType.systemDevTeam)
                holder.viewIcon.setImageResource(R.drawable.main_alarms_sns_undefine_icon);
            else if(rowData.getAlarmType() == AlarmType.systemQNA)
                holder.viewIcon.setImageResource(R.drawable.main_alarms_sns_undefine_icon);//R.drawable.main_alarms_common_info_icon);
            else {
                holder.viewIcon.setImageResource(R.drawable.main_alarms_system_message_icon);
            }
        }

        /**
         * 是否设置"消息免打扰"。
         *
         * @param alarmType 聊天item类型
         * @param dataId id
         * @return true表示是，否则不是
         */
        private boolean isSilent(int alarmType, String dataId){
            boolean isSilent = false;
            // 注意：目前"消息免打扰"设置只针对聊天消息哦
            if(!CommonUtils.isStringEmpty(dataId) && (alarmType == AlarmType.guestChat || alarmType == AlarmType.friendChat || alarmType == AlarmType.groupChat)){
                // TODO: 有优化余地——直接从Preferences里读有可能会在大量列表数据的情况下受Android的Preferences性能影响哦
                isSilent = !PreferencesToolkits.isChatMsgToneOpen(c(), dataId);
            }
            return isSilent;
        }

//        private void onBindViewHolder4Silent(LinearViewHolder holder){
//            // 消息提示是否静音的图标显示
//            holder.viewSilentIcon.setVisibility(isSilent ? View.VISIBLE : View.GONE);
//        }

        /**
         * 根据RecyclerView内容的变动，来决定无数据时的UI内容显示。
         */
        @Override
        protected void dataSetChanged(){
            // 当存在会话时就隐藏掉无数据提示组件
            boolean hasItems = (this.getListData().size() > 0);
            llNoAlarms.setVisibility(hasItems ? View.GONE : View.VISIBLE);
            listView.setVisibility(hasItems ? View.VISIBLE : View.GONE);
        }
    }

    /**
     * ViewHolder实现类。
     */
    private class LinearViewHolder extends RecyclerView.ViewHolder{

        // 标题
        TextView viewTitle = null;
        // 内容前缀组件（比如显示："[草稿]"、"[有人@我]"、"[10条消息]"等字样）
        TextView viewMsgPrefix = null;
        // 内容
        TextView viewMsgContent = null;
        // 时间
        TextView viewDate = null;
        // 未读数组件（非消息免打扰情况下显示）
        TextView viewFlagNum = null;
        // 未读红点（消息免打扰情况下显示）
        TextView viewFlagDot = null;
        ImageView viewIcon = null;
        // 消息静音图标目前仅用于群聊时
        ImageView viewSilentIcon = null;
        // 置顶的item图标
        ImageView viewIsAlwaysTop = null;
        // 标题左边的标签（目前主要用于显示陌生人标签，开发者也可以用于其它更多用途）
        TextView viewTitleLeftFlag = null;

        public LinearViewHolder(View itemView) {
            super(itemView);

            // item里的操作组件实例
            viewTitle = itemView.findViewById(R.id.main_alarms_list_item_titleView);
            viewMsgPrefix = itemView.findViewById(R.id.main_alarms_list_item_msgPrefixView);
            viewMsgContent = itemView.findViewById(R.id.main_alarms_list_item_msgView);
            viewDate = itemView.findViewById(R.id.main_alarms_list_item_dateView);
            viewFlagNum = itemView.findViewById(R.id.main_alarms_list_item_flagNumView);
            viewFlagDot = itemView.findViewById(R.id.main_alarms_list_item_flagDotView);
            viewIcon = itemView.findViewById(R.id.main_alarms_list_item_iconView);
            viewSilentIcon = itemView.findViewById(R.id.main_alarms_list_item_silentIconView);
            viewIsAlwaysTop = itemView.findViewById(R.id.main_alarms_list_item_alwayTopIconView);
            viewTitleLeftFlag = itemView.findViewById(R.id.main_alarms_list_item_titleLeftFlagView);
        }
    }
}
